/*
 * This file is part of J2ME VNC.
 *
 * Copyright (c) 2003 Michael Lloyd Lee
 *
 * J2ME VNC is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * J2ME VNC is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with J2ME VNC; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

package tk.wetnet.j2me.vnc;

import javax.microedition.lcdui.Canvas;
import javax.microedition.lcdui.Image;
import javax.microedition.lcdui.Graphics;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.ChoiceGroup;
import javax.microedition.lcdui.Gauge;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.AlertType;
import javax.microedition.lcdui.Alert;
import javax.microedition.lcdui.TextField;
import javax.microedition.lcdui.TextBox;
import javax.microedition.lcdui.Displayable;

import javax.microedition.rms.RecordStore;
import javax.microedition.rms.RecordEnumeration;

import java.io.IOException;

import tk.wetnet.vnc.RFBProto;
import tk.wetnet.vnc.DrawToable;
import tk.wetnet.util.Queue;

/** The VNC Canvas, receives draw requests from the VNC server via RFBProto
 * class, and the end user can the use this to communicated back to the server
 * key presses, and mouse movements.
 */
public final class VNCCanvas extends Canvas 
    implements CommandListener, DrawToable, Runnable {
    /** The VNC class that created me */
    private VNC midlet;
    
    /** Timeout for SMS key press. After X seconds this is called,
     * sending the currently selected letter to the server.
     * @category SMS Key System
     */
    private class Timeout extends java.util.TimerTask {
	
	public void run() {
	    nextLetter();
	    repaint();
	    serviceRepaints();
	}
    }
    
    
    /** Our timer, used for SMS time outs, and Active Refresh.
     * @category SMS Key System
     * @category Active Refresh
     */
    private java.util.Timer timer = new java.util.Timer();

    /** The current Timeout Object. 
     * @category SMS Key System
     */
    private Timeout timeout = null;

    /** Is Active Refresh currently enabled
     * @category Active Refresh
     */
    private boolean activeRefresh = false;

    /** The "working" icon */
    private Image redraw = null;
    /** The mouse icon */
    private Image mouse = null;
    /** The SMS icon */
    private Image sms = null; 
    /** The Nav mode icon */
    private Image nav = null; 

    /** If active refresh is enabled, and the server is not currently sending
     * data to the client, refresh the screen.
     * @category Active Refresh
     */
    protected void tick() {
	if( activeRefresh && !update ) {
	    try {
		midlet.rfb.requestUpdate( offx, offy, 
					  getWidth(), 
					  getHeight(), 
					  true );
	    } catch( IOException t ) {
		error( "Active Refresh: " + t.toString() );
	    }
	}
    }

    /** The amount to move the screen by */
    private int scrollby = (getWidth() / 5 );
   /** The amount to move the screen by */
    private int scrollbyY = (getHeight() / 5 );

    /** A tempory copy of the current screen used when the screen is
     * scrolled.
     */
    private Image tmpImg = Image.createImage( getWidth(), getHeight() );
    /** The back buffer used by the internal double buffering */
    private Image buffer = Image.createImage( getWidth(), getHeight() );
    /** The zoomed out image, created by draw(int x lots)*/
    private Image zoomOut = Image.createImage( getWidth(), getHeight() );

    /** The back-buffers graphics object
     * @todo Maybe rename?
     */
    private Graphics g = buffer.getGraphics();
    /** The zoomed out graphics object */
    private Graphics gzoomout = zoomOut.getGraphics(); 

    /** The current X offsets */
    private int offx, 
	/** The current Y offsets */
	offy;

    /** The number of pixels to move the mouse cursor by */
    private int mouseMoveBy = 1;
    
    /** The current view mode
     * @category view mode
     */
    private int viewMode = NORMAL;
    /** Normal, none-zoomed out view mode
     * @category view mode
     * @value 0
     */
    private static final int NORMAL = 0;
    /** Zoomed out view mode
     * @category view mode
     * @value 1
     */
    private static final int ZOOM_OUT = 1;

    /** The current number pad mode
     * @category number pad mode
     */

    /** SMS Mode
     * Arrow keys send left/right/up/down
     * Number keys send SMS
     * Number keys come first
     * @category Input mode
     * @value 1
     */
    private static final int SMS_MODE = 2;

    /** Type Mode
     * Arrow keys send left/right/up/down
     * None-arrow keys come first
     * @category INPUT mode
     * @value 2
     */
    private static final int TYPE_MODE = 3;


    /** Move the screen
     * None-arrow keys "type"
     * @category Input Mode
     * @value 0
     */
    private static final int NAV_MODE = 0;

    /** Move the mouse cursor 
     * @category Input Mode
     * @value 1
     */
    private static final int MOUSE_MODE = 1;

    /**
     * @category Input Mode
     */
    private int inputMode = NAV_MODE;
    
    private Command modeCmd = new Command( "#mode#", Command.SCREEN, 1 );
    private Command refreshCmd = new Command( "#refresh#", Command.SCREEN, 2 );
    private Command moveMouseCmd = new Command( "#call_mouse#", 
						Command.SCREEN, 3 );
    private Command textCmd = new Command( "#enter_text#", Command.SCREEN, 4 );
    private Command modeOKCmd = new Command( "#ok#", Command.OK, 1 );
    private Command exitCmd = new Command( "#exit#", Command.EXIT, 10 );
    private Command optionsCmd = new Command( "#options#", Command.SCREEN, 2 );

    private Command cancelCmd = new Command( "#cancel#", Command.BACK, 1 );

    private Command altCmd = new Command( "#alt#", Command.SCREEN, 5 );
    private Command metaCmd = new Command( "#meta#", Command.SCREEN, 6 );
    private Command ctrlCmd = new Command( "#control#", Command.SCREEN, 5 );

    private Command caseCmd = new Command( "#case#", Command.SCREEN, 1 );
    private Command enterCmd = new Command( "#enter#", Command.SCREEN, 1 );

    /** Are the ALT, Control or Meta keys pressed */
    private boolean alt = false, meta = false, ctrl = false;

    private String[] viewModes = {"#normal#", "#full_screen#" };

    private String[] inputModes = { "#navigation#", 
				    "#mouse_mode#", 
				    "#sms_mode#",
				    "#type#" };
    
    private ChoiceGroup viewCh = new ChoiceGroup("#view#", 
						 ChoiceGroup.EXCLUSIVE, 
						 viewModes, null);

    private ChoiceGroup inputModeChoice = new ChoiceGroup("#game_keys#", 
						  ChoiceGroup.EXCLUSIVE, 
						  inputModes, null);


    private TextBox sendStringBox = new TextBox( "#send#", "", 255, 
						 TextField.ANY ); 
    
    private Form options = new Form( "#optionst#" );
    private Gauge scrollX = new Gauge( "#scroll_amount#", true, 4, 0 );
    //private Gauge scrollY = new Gauge( "#scrolly#", true, 4, 0 );

    private Gauge mouseMove = new Gauge( "#mouse_move#", true, 20, 1 );

    private ChoiceGroup aR = new ChoiceGroup( "", ChoiceGroup.MULTIPLE );

    private Form modeForm = new Form( "#mode#" );
        
    /** The out-of-the-event-thread event thread */
    private Thread worker = new Thread( this );
    /** Is the system currently running */
    private boolean running = true;

    /** Is the local cursor mode enabled
     * When enabled, only mouse presses are sent to the server, 
     * all others only change the mouseX and mouseY variables
     * @see mouseX
     * @see mouseY
     */
    private boolean localCursor = false;

    /** Number of outstanding GUI actions to be processed, or still being 
     * processed. Used by the paint method, to show the "working" icon.
     */
    private int requests = 0;

    /** The record store holding the Macros. */
    private RecordStore rs;

    /** Empty constructor used by the About screen for details about the canvas
     */
    VNCCanvas() { }
    
    
    /** Creates a VNC Canvas for normal use. 
     */
    VNCCanvas( VNC m ) {
	midlet = m;
	addCommand( metaCmd );
	addCommand( ctrlCmd );
	addCommand( altCmd );
	addCommand( modeCmd );
	addCommand( moveMouseCmd  );
	addCommand( refreshCmd );
	addCommand( textCmd );
	addCommand( exitCmd );
	addCommand( optionsCmd );
	addCommand( enterCmd );
	
	//#ifdef DEBUG
	//addCommand( m.log );
	//#endif

	greyOut(g, 0, 0, getWidth(), getHeight());
	greyOut(gzoomout, 0, 0, getWidth(), getHeight());

	aR.append( "#activerefresh#", null );
	aR.append( "#local_cursor#", null );

	options.append( scrollX );
	//options.append( scrollY );
	options.append( mouseMove );
	options.append( aR );
	options.addCommand( modeOKCmd );
	options.addCommand( modeOKCmd );

	sendStringBox.addCommand( modeOKCmd );
	sendStringBox.addCommand( enterCmd );
	sendStringBox.addCommand( cancelCmd  );
	sendStringBox.setCommandListener( this );

	setCommandListener( this );

	modeForm.append( viewCh );
	modeForm.append( inputModeChoice  );
	modeForm.addCommand( modeOKCmd );
	sendStringBox.addCommand( cancelCmd  );

	modeForm.setCommandListener( this );

	options.setCommandListener( this );

	for( int i = 0; i<5;i++) {
	    eventCashe.push( new Event() );
	}

	try {
	    redraw = Image.createImage( "/redraw.png" );
	    
	    mouse = Image.createImage( "/mouse.png" );
	    sms = Image.createImage( "/key.png" );
	    nav = Image.createImage( "/nav.png" );
	} catch( IOException t ) {
	    System.err.println( "Create Image: key: " + t );
	}

	timer.schedule( new tk.wetnet.j2me.vnc.Ticker( this ), 2000, 1500 );

	//#ifdef DEBUG
	//# VNC.log( "hasRepeatEvents() " + hasRepeatEvents() );
	//#endif

	try {
	    rs = RecordStore.openRecordStore( "cmds", true );
	    if( rs.getNumRecords() == 0 ) {
		byte[] s = "Ctrl Alt Del|<\\c<\\a!\\d>\\a>\\c".getBytes();
		rs.addRecord( s, 0, s.length );
	    }

	    RecordEnumeration re = rs.enumerateRecords( null, null, false );
	    while( re.hasNextElement() ) {
		int id = re.nextRecordId();
		String current = new String( rs.getRecord(id) );
		String title = current;
		if( current.indexOf( "|" ) > 0 ) {
		    title = current.substring(0, current.indexOf( "|" ) );
		}
		HostCommand cm = new HostCommand( title, Command.ITEM, 
						  10, id );
		addCommand( cm );
	    }
	    //TODO: Better Error Handling Code!
	} catch( javax.microedition.rms.RecordStoreException t ) {
	    System.err.println( "VNC() : t " + t.toString() );
	    t.printStackTrace();
	}
	worker.start();
    }

    /** The Mouse X location */
    private int mouseX = 0, 
	/** The mouse Y location */
	mouseY = 0;
    
    /** Processes the commands events out side of the event queue.
     * @see commandAction
     * @see events
     * @see eventsCashe
     * @see Event
     * @see run()
     * @todo redo exception handling
     * @todo move to multiply smaller methods?
     */
    private void commandRunner( Command c, Displayable d ) {
	//#ifdef DEBUG
	//# VNC.log( "commandAction" + c.getLabel() );
	//#endif

	if( c == refreshCmd ) {
	    try {
		if( viewMode == ZOOM_OUT ) {
		    midlet.rfb.requestUpdate( 0, 0, 
					      midlet.rfb.x, 
					      midlet.rfb.y,
					      false );
		} else {
		    midlet.rfb.requestUpdate( offx, offy, 
					      getWidth(), 
					      getHeight(), 
					      false );
		}
	    } catch(Throwable t ){
		error("cmd Refresh" + t );
	    }
	    repaint();
	    serviceRepaints();
	} else if ( c == optionsCmd ) {
	    midlet.me.setCurrent( options );
	} else if ( c == metaCmd ) {
	    meta = !meta;
	    try {
		midlet.rfb.key( 0xffe7, meta );
	    } catch( Throwable t ) {
		error( t.toString() );
	    }
	} else if ( c == altCmd ) {
	    alt = !alt;
	    try {
		midlet.rfb.key( 0xffe9, alt );
	    } catch( Throwable t ) {
		error( t.toString() );
	    }
	} else if ( c == ctrlCmd ) {
	    ctrl = !ctrl;
	    try {
		midlet.rfb.key( 0xffe3, ctrl );
	    } catch( Throwable t ) {
		error( t.toString() );
	    }
	} else if ( c == exitCmd ) {
	    close();
	    midlet.notifyDestroyed();
	} else if ( c == textCmd ) {
	    midlet.me.setCurrent( sendStringBox );
	} else if ( c == modeCmd ) {
	    midlet.me.setCurrent( modeForm );
	} else if ( c == cancelCmd ) {
	    // Reset settings.
	    sendStringBox.setChars( null, 0, 0);
	    inputModeChoice.setSelectedIndex( inputMode, true );
	    viewCh.setSelectedIndex( viewMode, true ); 
	    midlet.me.setCurrent( this );
	} else if( c == caseCmd ) {
	    uppercase = !uppercase;
	} else if( c == enterCmd ) {
	    try { 
		if( d == sendStringBox ) {
		    midlet.rfb.sendString( sendStringBox.getString() + "\n" );
		} else {
		    midlet.rfb.sendString( "\n" );
		}
	    } catch( Throwable t ) {
		error( "Sending Enter: " + t.toString() );
	    }
	    midlet.me.setCurrent( this );
	} else if( c == modeOKCmd ) {
	    if( d == modeForm ) {
		inputMode = inputModeChoice.getSelectedIndex();

		viewMode = viewCh.getSelectedIndex();

		
		if( inputMode == SMS_MODE ) {
		    addCommand( caseCmd );
		} else {
		    removeCommand( caseCmd );
		}
	    } else if( d == sendStringBox ) {
		try {
		    midlet.rfb.sendString( sendStringBox.getString() );
		} catch( Throwable t ){
		    error( "modeOK: sendString: " + t );
		}
	    } else if( d == options ) {
		scrollby  = ( scrollX.getValue() + 1 ) *  (getWidth() / 5 );
		scrollbyY = ( scrollX.getValue() + 1 ) * (getHeight() / 5 );
		mouseMoveBy = mouseMove.getValue();
		if (mouseMoveBy==0) {
		    mouseMoveBy=1;
		} 
		
		activeRefresh = aR.isSelected( 0 );
		localCursor = aR.isSelected( 1 );
	    }
	    
	    midlet.me.setCurrent( this );
	} else if( c == moveMouseCmd ) {
	    try {
		mouseX = offx + ( getWidth() / 2 );
		mouseY = offy + ( getHeight() / 2 );
		midlet.rfb.mouse( mouseX, 
				  mouseY,
				  0 );
		midlet.rfb.requestUpdate( mouseX, mouseY, 32, 32, true );
	    } catch( Throwable t ) {
		error("cmd Refresh" + t );
	    }
	} else if ( c instanceof HostCommand ) {
	    HostCommand cmd = (HostCommand)c;	
	    String tmp;
	    try {
		tmp = new String( rs.getRecord( cmd.id ) );
	    } catch( Throwable t ) {
		tmp = "|";
	    }
	    try {
		stringToKeys(  tmp.substring
			       ( tmp.indexOf( "|" ) + 1, 
				 tmp.length()) );
	    } catch( Throwable t ) {
		error( "Custom Command: " + t );
	    }
	}
	requests--;
	if( requests <= 0 ) {
	    requests = 0;
	}
	repaint();
	serviceRepaints();
    }

    /** Places and Event representing the current command action in the
     * event queue.
     * @see events
     * @see Event
     * @see CommandListener.commandAction(Command, Displayable )
     */
    public void commandAction(Command co, Displayable di) {
	Event e = null;

	requests++;
	repaint();

	if( eventCashe.isEmpty() ) {
	    e = new Event();
	} else {	   
	    e = (Event)eventCashe.pop();
	}
	
	boolean notify = events.isEmpty();
	e.mode = e.COMMAND_ACTION;
	e.d = di;
	e.c = co;

	events.push( e );
	if( notify ) {
	    try {
		worker.notifyAll();
	    } catch( IllegalMonitorStateException i ) {
		System.err.println( i.toString() );
	    }
	}
    }

    /** Upper or lower case
     * @category SMS Key System
     */
    private boolean uppercase = true;

    /** The number/#* key pressed last.
     * @category SMS Key System
     */
    private byte lastPressed = -1;

    /** The number of times the current number key has been pressed % by the
     * number of values this key can be.
     * @category SMS Key System
     */
    private byte keyCounter = -1;

    /** The key/number of times keyboard layout.
     * e.g. if key '1' has been pressed twice you get 'B'.
     * @category SMS Key System
     */
    private char[][] keys = { { '0', ' ' },                
			      { '.', ',', '!', '1' },    
			      { 'A', 'B', 'C', '2' },      
			      { 'D', 'E', 'F', '3' },      
			      { 'G', 'H', 'I', '4' }, 
			      { 'J', 'K', 'L', '5' },
			      { 'M', 'N', 'O', '6' },
			      { 'P', 'Q', 'R', 'S', '7' },
			      { 'T', 'U', 'V', '8' },
			      { 'W', 'X', 'Y', 'Z', '9' },
			      { '#' },
			      { '*' } };


    /** Cleans up & leaves the server in a reasonable state. 
     * Close the open record stores.
     * Unpress any meta keys (Meta/Alt/Cntrl)
     * Ends the event-processing thread.
     * Ends the RFB Protos thread.
     * Closes the RFB IO streams
     */
    protected void close() {
	try {
	    rs.closeRecordStore();
	} catch( Throwable t ) {}

	try {
	    running = false;
	    worker.notify();

	    if ( meta ) {
		try {
		    midlet.rfb.key( 0xffe7, false );
		} catch( Throwable t ) {
		    error( t.toString() );
		}
	    } 
	    if ( alt ) {
		try {
		    midlet.rfb.key( 0xffe9, false );
		} catch( Throwable t ) {
		    error( t.toString() );
		}
	    } 
	    if ( ctrl ) {
		try {
		    midlet.rfb.key( 0xffe3, false );
		} catch( Throwable t ) {
		    error( t.toString() );
		}
	    }		    		  
	    
	    	    
	    midlet.rfb.running = false;
	    midlet.rfb.sin.close();
	    midlet.rfb.sout.close();
	} catch( Throwable t ) { }
    }
    

    /** Sends the current letter (in SMS mode) to the server, and refreshes
     * the screen.
     * @todo Rename method!
     * @category SMS Key System
     */
    private void nextLetter() {
	try {
	    if( uppercase ) {
		midlet.rfb.key( keys[lastPressed][keyCounter], true );
		midlet.rfb.key( keys[lastPressed][keyCounter], false );
	    } else {
		midlet.rfb.key( Character.toLowerCase
				( keys[lastPressed][keyCounter]) , true );
		midlet.rfb.key( Character.toLowerCase
				( keys[lastPressed][keyCounter]) , false );
	    }
	} catch( Throwable tt ){
	    error("keyPressed: tt: " + tt );
	}
	keyCounter = -1;
	lastPressed = -1;
	try {
	    midlet.rfb.requestUpdate( offx, offy, 
				      getWidth(), 
				      getHeight(), 
				      true );
	} catch( Throwable thr ) {
	    error("keyPressed: thr: " + thr );
	}
    }

    /** The input events (key presses and command-events) add Event items
     * into the events queue, to reduce the amount of time spent in the
     * event thread. 
     * @see eventCashe
     * @see run()
     * @see keyPressed()
     * @see Event
     */
    private Queue events = new Queue( );

    /** To prevent memory allocations, a cashe of Event objects is kept.
     * @see events
     * @see run()
     * @see keyPressed()
     * @see Event
     */
    private Queue eventCashe = new Queue( ); 

    /** Processes events. This is done outside of the system event thread
     * as the IO opperations might take a very long time to complete,
     * or even block all together, and we do not want UI to freeze up
     * on the user.
     * while I have events to process, and I'm running
     *    pop the event, and process it.
     *    Push the event back onto the cashe.
     * Sleep for 1s, or until new events are dispatched.
     */
    public void run() {
	while( running ) {
	    while( !events.isEmpty() && running ) {
		Event e = (Event)events.pop();
		switch( e.mode ) {
		case Event.KEY_PRESSED:
		    kp( e.keyCode );
		    break;
		case Event.COMMAND_ACTION:
		    commandRunner( e.c, e.d );
		    e.c = null;
		    e.d = null;
		    break;
		}

		eventCashe.push( e );
	    }
	    
	    try {
		synchronized( worker ) { 
		    worker.wait( 1000 );
		}
	    } catch ( InterruptedException ie ) {
	
	    } catch ( IllegalMonitorStateException emse ) {
		
	    }
	}
    }
    
    /** The input events (key presses and command-events) add Event items
     * into the events queue, to reduce the amount of time spent in the
     * event thread. 
     */
    private class Event extends tk.wetnet.util.Queue.QueueEntry {
	private static final int KEY_PRESSED = 0;
	private static final int COMMAND_ACTION = 1;
	private int mode = KEY_PRESSED;	
	
	private int keyCode = 0;
	private Command c = null;
	private Displayable d = null;
    }


    /** Simply adds the event into the event queue, and notifies the event
     * thread.
     * @random_idea Add scroll mode, where it grays out screen, then adds
     *             A small box to represent the current location, and 
     *             you move that then it refresh the screen to that location.
     * @random_idea Fish eye mode?
     * WAS: keyPressed
     */
    public void keyReleased ( int keyCode ) {
	Event e = null;

	requests++;
	repaint();

	if( eventCashe.isEmpty() ) {
	    e = new Event();
	} else {	   
	    e = (Event)eventCashe.pop();
	}
	
	boolean notify = events.isEmpty();
	e.mode = e.KEY_PRESSED;
	e.keyCode = keyCode;
	events.push( e );
	if( notify ) {
	    try {
		worker.notifyAll();
	    } catch( IllegalMonitorStateException i ) {
		System.err.println( i.toString() );
	    }
	}
    }

    /** if the mode is the Mouse Mode, repeated keys move the local mouse
     * pointer, but does not send this to the server.
     */
    public void keyRepeated( int keyCode ) {
	if ( inputMode == MOUSE_MODE ) {
	    switch( getGameKeyPriorityGames( keyCode ) )  {
	    case DOWN: 
		mouseY+=mouseMoveBy;
		break;
	    case UP:
		mouseY-=mouseMoveBy;
		break;
	    case LEFT:
		mouseX-=mouseMoveBy;
		break;
	    case RIGHT:
		mouseX+=mouseMoveBy;
		break;
	    }
	    if (mouseX < 0) {
		mouseX=0;
	    } else if (mouseX > midlet.rfb.x) {
		mouseX=midlet.rfb.x;
	    } 	    
	    if (mouseY < 0) {
		mouseY=0;
	    } else if (mouseY > midlet.rfb.y) {
		mouseY=midlet.rfb.y;
	    }
	}
	repaint();
	serviceRepaints();
    }

    private void sendKey( int keyCode ) {
	// Add a hashtable to let uses change keycodes.

	try {
	    midlet.rfb.key( keyCode, true );
	    midlet.rfb.key( keyCode, false );
	    midlet.rfb.requestUpdate( offx, offy, getWidth(), 
				      getHeight(), true );
	} catch( Throwable t1 ){
	    error("keyPressed: t1: " + t1 );
	}
    }

    private boolean sendSMS( int keyCode ) {
	byte t = -1;
	if( keyCode >= 48 && keyCode <= 57 ) {
	    t = (byte)(keyCode - 48);
	} else if( keyCode == KEY_STAR ) {
	    t = 11;
	} else if( keyCode == KEY_POUND ) {
	    t = 10;
	}
	if( t == -1 ) return false;
	if( t != lastPressed && lastPressed != -1 ) {
	    try { 
		if( uppercase ) {
		    midlet.rfb.key( keys[lastPressed][keyCounter], 
				    true );
		    midlet.rfb.key( keys[lastPressed][keyCounter], 
				    false );
		} else {
		    midlet.rfb.key( Character.toLowerCase
				    ( keys[lastPressed][keyCounter] ), 
				    true );
		    midlet.rfb.key( Character.toLowerCase
				    ( keys[lastPressed][keyCounter] ), 
				    false );
		}
	    } catch( Throwable tt ){
		error("keyPressed: tt: " + tt );
	    }
		    keyCounter = -1;
	} else {
	    if( timeout != null ) {
		timeout.cancel();
	    }
	    timeout = new Timeout();
	    timer.schedule( timeout, 2000 );
	}
	lastPressed = t;
	keyCounter++;
	if( keyCounter == keys[lastPressed].length ) {
	    keyCounter = 0;
	}
	return true;
    }   

    private boolean sendMouse( int keyCode ) {
	int buts = 0;
	boolean doneSomething = false;

	switch( getGameKeyPriorityGames( keyCode ) ) {
	case DOWN: 
	    doneSomething = true;
	    mouseY+=mouseMoveBy;
	    break;
	case UP:
	    doneSomething = true;
	    mouseY-=mouseMoveBy;
	    break;
	case LEFT:
	    doneSomething = true;
	    mouseX-=mouseMoveBy;
	    break;
	case RIGHT:
	    doneSomething = true;
	    mouseX+=mouseMoveBy;
	    break;
	case FIRE:
	    doneSomething = true;
	    buts = 1;
	    break;		
	case GAME_A:
	    doneSomething = true;
	    buts = 2; 
	    break;
	}

	if( !doneSomething ) return doneSomething;

	if (mouseX < 0) {
	    mouseX=0;
	} else if (mouseX > midlet.rfb.x) {
	    mouseX=midlet.rfb.x;
	} 	    
	if (mouseY < 0) {
	    mouseY=0;
	} else if (mouseY > midlet.rfb.y) {
	    mouseY=midlet.rfb.y;
	}
	if ( !localCursor || buts > 0 ) {       	    
	    //#ifdef DEBUG
	    //# VNC.log( "mousex: " + mouseX + "mouseY"+ mouseX ) ;
	    //#endif
	    try {
		midlet.rfb.mouse( mouseX, 
				  mouseY,
				  ( buts > 0 ? 1 : 0) );
		if( buts != 0 ) {
		    midlet.rfb.mouse( mouseX, 
				      mouseY,
				      0 );
		    if ( buts == 2 ) {
			midlet.rfb.mouse( mouseX, 
					  mouseY,
					  1 );
			midlet.rfb.mouse( mouseX, 
					  mouseY,
					  0 );
		    } 
		    
		}		
		
		midlet.rfb.requestUpdate( offx, offy, getWidth(), 
					  getHeight(), true );
		
	    } catch ( Throwable t7 ) {
			error( "MouseMove: " + t7 );
	    }
	}
	return doneSomething;
    }

    private boolean sendNav( int keyCode ){
	int sb = 0;

	tmpImg.getGraphics().drawImage( buffer, 0, 0, g.TOP|g.LEFT );
	//Image i = Image.createImage( buffer );
	switch( getGameKeyPriorityGames( keyCode ) )  {
	case LEFT: 
	    if( viewMode == NORMAL ) 
		sb = scrollby;		
	    else if (viewMode == ZOOM_OUT ) 
		sb = getWidth();
	    if( offx - sb < 0 ) {
		sb = offx;
	    }
	    offx-=sb;
	    
	    g.drawImage( tmpImg, sb, 0, g.TOP|g.LEFT );
	    
	    greyOut( g, 0, 0, sb, getHeight() );
	    try {
		midlet.rfb.requestUpdate( offx, offy, 
					  sb, 
					  getHeight(), false );
	    } catch( Throwable t4 ){
		error("keyPressed: t4: " + t4 );
	    }	
	    return true;
	    //break;
	    
	case RIGHT: 
	    if( viewMode == NORMAL )
		sb = scrollby;
	    else if (viewMode == ZOOM_OUT ) 
		sb=getWidth();
	    
	    if(offx + sb > (midlet.rfb.x - getWidth() ) ) {
		sb = (midlet.rfb.x - getWidth() ) - offx ;
		System.out.println( sb );
	    }
	    
	    offx+=sb; 
	    g.drawImage( tmpImg, -sb, 0, g.TOP|g.LEFT );
	    
	    greyOut( g, getWidth() - sb, 0, 
		     scrollby , getHeight() );
	    try {
		midlet.rfb.requestUpdate( offx + getWidth() 
					  - sb, offy, 
					  sb, 
					  getHeight(), false );
	    } catch( Throwable t5 ){
		error("keyPressed: t5: " + t5 );
	    }
	    return true;
	    //break;  
	case UP: 
	    if( viewMode == NORMAL )
		sb = scrollbyY;
	    else if ( viewMode == ZOOM_OUT ) 
		sb=getHeight();
	    
	    if(offy - sb < 0) { 
		sb = offy;
	    }
	    
	    offy-=sb; 
	    g.drawImage( tmpImg, 0, sb, g.TOP|g.LEFT );
	    
	    greyOut( g, 0, 0, getWidth(), sb);
	    try {
		midlet.rfb.requestUpdate( offx, offy, 
					  getWidth(),
					  sb, false );
	    } catch( Throwable t6 ){
		error("keyPressed: t6: " + t6 );
	    }
	    return true;
	    //break;
	    
	case DOWN: 
	    if( viewMode == NORMAL )
		sb = scrollbyY;
	    else if ( viewMode == ZOOM_OUT ) 
		sb=getHeight();
	    
	    if(offy + sb > (midlet.rfb.y - getHeight() ) ) {
		System.out.println( sb ) ;
		sb = (midlet.rfb.y - getHeight()) - offy;
		System.out.println( sb + " " + offy + " " 
				    + (midlet.rfb.y - getHeight() )
				    + " " + (offy+sb) );
	    }
	    
	    offy+=sb;
	    g.drawImage( tmpImg, 0, -sb, g.TOP|g.LEFT );
	    
	    greyOut( g, 0, getHeight() - sb,
		     getWidth() , sb );
	    try {
		midlet.rfb.requestUpdate( offx, offy + getHeight() 
					  - sb , 
					  getWidth(), 
					  sb, false );
	    } catch( Throwable t1 ){
		error("keyPressed: t1: " + t1 );
	    }
	    return true;
	    //break; 
	}
	return false;
    }

    private final int _KEY_UP = 0,
	_KEY_DOWN = 1,
	_KEY_LEFT = 2,
	_KEY_RIGHT = 3,
	_KEY_FIRE = 4,
	_KEY_SNDFIRE = 5,
	_KEY_CHANGE_MODE = 6;

    // TODO: READ FROM FILE!
    int[] actionKeys = new int[]{ KEY_NUM2,
		   KEY_NUM8,
		   KEY_NUM4,
		   KEY_NUM6,
		   KEY_NUM5,
		   KEY_POUND,
		   KEY_STAR };
    
    /** priority  */
    private int getGameKeyPriorityGames( int keyCode ) {
	if( getGameAction( keyCode ) != 0 ) 
	    return getGameAction( keyCode );
	if( keyCode == actionKeys[ _KEY_DOWN ] ) 
	    return DOWN;
	else if( keyCode == actionKeys[ _KEY_LEFT ] ) 
	    return LEFT;
	else if( keyCode == actionKeys[ _KEY_RIGHT ] ) 
	    return RIGHT;
	else if( keyCode == actionKeys[ _KEY_UP ] ) 
	    return UP;	
	else if( keyCode == actionKeys[ _KEY_FIRE ] ) 
	    return FIRE;
	else if( keyCode == actionKeys[ _KEY_SNDFIRE ] ) 
	    return GAME_A;
	return 0;
    }

    /** The KeyPressed implementation
     */
    private void kp( int keyCode ) {
	boolean doneSomething = false;
	if( keyCode == actionKeys[ _KEY_CHANGE_MODE ] ) {	    
	    inputMode++;
	    if( inputMode > TYPE_MODE ) inputMode = NAV_MODE;
	    doneSomething = true;
	    inputModeChoice.setSelectedIndex( inputMode, true );
	} else if( inputMode == TYPE_MODE ) {
	    if( keyCode > 0 ) {
		sendKey( keyCode );
		doneSomething = true;
	    } // else switch gamekey move
	} else if( inputMode == MOUSE_MODE ) {
	    doneSomething = sendMouse( keyCode );
	} else if( inputMode == SMS_MODE ) {
	    doneSomething = sendSMS( keyCode );
	} else if( inputMode == NAV_MODE ) {
	    doneSomething = sendNav( keyCode );
	} 

	if( !doneSomething ) {
	    if( keyCode > 0 )
		sendKey( keyCode );
	}

	requests--;
	if( requests <= 0 ) {
	    requests = 0;
	}

	repaint();
	serviceRepaints();
    }

    
    /** grays out an image.
     * @param gr The graphics object to grey out
     * @param x the x offset
     * @param y the y offset
     * @param w the width
     * @param h the height
     */
    private void greyOut( Graphics gr, int x, int y, int w, int h ) {	
	g.setColor( 0xffffff );
	g.fillRect( x, y, w, h );
	for(int i = w-1; i>0; i-=2 ) {
	    for(int f = h-1; f>0; f-=2 ) {
		gr.setColor( 100, 100, 100 );
		gr.fillRect( x+i, y+f, 1, 1 );
	    }
	}	
    }

    /** Is the server currently sending an update */
    private boolean update = false;

    /** The server has started an update
     * @see DrawOnMe.startUpdate()
     */
    public void startUpdate(){
	update=true;
	repaint();
	serviceRepaints();
    }

    /** The server has finished sending an update.
     * @see DrawOnMe.endUpdate()
     */
    public void endUpdate(){
	update=false;
	repaint();
	serviceRepaints();
    }

    /** Starts an alarm on the servers request 
     * @see DrawOnMe.ringBell()
     */
    public void ringBell() {
	AlertType.ALARM.playSound( midlet.me );
    }

    /** Null implementation
     * @todo remove all together to save space?
     * @see DrawOnMe.copyRect()
     */
    public void copyRect( int x, int y, int w, int h, int srcx, int srcy){
	//#ifdef DEBUG
	//# VNC.log( "copyRect" );
	//#endif
	/*if( (x > offx) && (x < offx+getWidth() ) 
	  &&  (y > offy) && (y < offy+getHeight() )){
	  Image i = Image.createImage( w, h );
	  i.getGraphics().drawImage( buffer, x-offx, y-offy, 
	  g.TOP|g.LEFT );
	  gr.drawImage( i, srcx, srcy, g.TOP|g.LEFT ); 
	  }*/
    }
    
    /** Cashes the local screen width / remote screen width
     * This is used by the scaling system
     * @note this uses the lowest of this and divy
     * @todo remove one of the two/have a nasty scale option?
     */
    private int divx = 0;
    /** Cashes the local screen height / remote screen height 
     * This is used by the scaling system
     * @note this uses the lowest of this and divx
     * @todo remove one of the two/have a nasty scale option?
     */
    private int divy = 0;

   /** Draws a square on the back buffer
    * Also draws a scaled square on the zoomed out image if it is a square
    * larger than or equal to the number of pixels covered by one scaled pixel
    * or if x and y are excaly on the top right pixel of a zoomed out section
    * @todo move the creating of RGB to RFBProto, so only sends one
    *       int?
    * @todo better scaled image.
    * @todo play with with else statement, && or ||
    * @see DrawOnMe.draw()
    */
    public void draw(int red, int green, int blue, int x, int y, 
		     int w, int h) {


	gzoomout.setColor( red, green, blue );

	if( w >= divx || h >= divy ) {
	    gzoomout.fillRect( ( x==0 ? 0 : x/divx ),
			       ( y==0 ? 0 : y/divy),
			       ( w==0 ? 1 : (w/divx) + 1 ),
			       ( h==0 ? 1 : (h/divy) + 1 ) );	    
	} else if ( ( x % divx == 0  ) && ( y % divy == 0  ) ) {
	    gzoomout.fillRect(( x==0 ? 0 : x/divx ),
			      ( y==0 ? 0 : y/divy ),
			      1 , 1 );		
	}
	
	g.setColor( red, green, blue );
	g.fillRect( x-offx, y-offy , w, h );
    }

    /** 
     * @see Canvas.pointerPressed( int, int )
     */
    protected void pointerPressed(int x, int y) {
	mouseX = offx + x;
	mouseY = offy + y;
	try {
	    midlet.rfb.mouse( mouseX, 
			      mouseY,
			      1 );
	    midlet.rfb.mouse( mouseX, 
			      mouseY,
			      0 );
	    midlet.rfb.requestUpdate( mouseX, mouseY, 32, 32, 
				      true );
	} catch ( Throwable t ) {
	    error( "MouseMove: " + t );
	}
    }

    /** Paints either the zoomed out image, or normal image (depending on
     * mode), the "working" icon if the system is currently working
     * plus any mode icons.
     * @see requests
     * @see update
     * @param gr The Object to paint on
     */
    public void paint( Graphics gr ) {
	switch(viewMode) {
	case ZOOM_OUT: 
	    gr.drawImage(zoomOut, 0, 0, g.TOP|g.LEFT );
	    gr.setColor( 0, 255, 0 );

	    gr.drawRect( (offx==0?0:( offx / divx ) ),
			 (offy==0?0:(offy / divy)),
			 getWidth() / divx,
			 getHeight() / divy);
	    break;
	default:
	    gr.drawImage(buffer, 0, 0, g.TOP|g.LEFT );
	    if( inputMode == MOUSE_MODE ){ 
		gr.setColor( 255, 0, 0 );
		gr.drawRect( mouseX - offx-1, mouseY - offy-1, 2, 2 );
	    }
	    break;
	}
	if( update || requests > 0 ) {
	    gr.drawImage( redraw, getWidth(), getHeight(), 
			  gr.BOTTOM | gr.RIGHT );
	} 
	if( inputMode == MOUSE_MODE  ) {
	    gr.drawImage( mouse, getWidth()-16, getHeight(), 
			  gr.BOTTOM | gr.RIGHT );
	} else if( inputMode == SMS_MODE ) {
	    gr.drawImage( sms, getWidth()-16, getHeight(), 
			  gr.BOTTOM | gr.RIGHT );
	} else if( inputMode == NAV_MODE ) {
	    gr.drawImage( nav, getWidth()-16, getHeight(), 
			  gr.BOTTOM | gr.RIGHT );
	}
	if ( inputMode == SMS_MODE && lastPressed != -1 ) {
	    gr.setColor( 0 );
	    gr.fillRect( 0,  getHeight()-gr.getFont().getHeight(), 
			 gr.getFont().charWidth(keys[lastPressed][keyCounter]),
			 gr.getFont().getHeight() );
	    gr.setColor( 0xFFFFFF );
	    if( uppercase ) {
		gr.drawChar( keys[lastPressed][keyCounter], 0, 
			     getHeight()-gr.getFont().getHeight(), 
			     gr.TOP|gr.LEFT );

	    } else {
		gr.drawChar( Character.toLowerCase
			     ( keys[lastPressed][keyCounter] ), 0, 
			     getHeight()-gr.getFont().getHeight(), 
			     gr.TOP|gr.LEFT );
	    }
	}

	
    }

    /** An error has ocoured, so display an error message, and only on
     * button "Exit", which when pressed will call "close", and clean up.
     */
    public void error( String error ) {
	System.out.print( error );
	Form form = new Form( "#error#" );
	try { 
	    form.append( midlet.aboutImage );
	} catch ( Throwable t ) {}

	form.append( error );
	form.addCommand( exitCmd );
	form.setCommandListener( this );
	midlet.me.setCurrent( form );
    }

    /** The VNC server is ready for us, so change from the connection screen
     * to the canvas, pre-calculate values needed, then request an update.
     */
    public void ready() {
	midlet.me.setCurrent( this );
	divx = (midlet.rfb.x / getWidth()) + 1; 
	divy = (midlet.rfb.y / getHeight()) + 1; 
	if( divx < divy ) divx = divy;
	if( divy < divx ) divy = divx;
	try {
	    midlet.rfb.requestUpdate( 0, 0, 
				      getWidth(),
				      getHeight(), false );
	} catch( Throwable t ){
	    error( "ready: " + t );
	}
    }

    /** The RFB server has got a stage closer to being connected
     * @see VNC.incConnectionStatus( )
     */
    public void incConnectionStatus( ) {
	midlet.incConnectionStatus( );
    }


    /** Intrepts a string as a Macro, and runs it.
     * ?move this too the About box (with the "hidden" options)
     * ?and on this side have a machine friendly, human unreadable format?
     * ? harder to then parse back for editing.
     *
     *
     * cmd (action)
     * i.e.
     * !h!e!l!l!e - sends hello.
     * <\c<\a!\d>\a>\c - ctrl alt delete.
     * 
     *
     * letter combos:
     * x literal.
     * \d Delete
     * \a Alt
     * \m Meta
     * \c Control
     * \n newline
     * \\ \
     * \p |
     * 
     *
     * cmds:
     * <          - key down
     * >          - key up
     * !          - key down then up (ie press)
     * mXXXX,XXXX - Mouse Move To, must be four digits 
     * c          - Click (primary mouse button)
     * MXXXX,XXXX - Move Screen To, follows the same standard as 'm'

     * @param s The String
     * @todo Rename?
     * @todo Rewrite!
     * @todo Add missing control letters
     * @throws Throwable
     */


    private void stringToKeys( String s ) throws Throwable {
	boolean incUpdate = true; // When the macro is over, can I get away
	                          // with just doing a incremental update.
	try {
	    int len = s.length();
	    
	    for( int i = 0; i<len;i++ ) {
		if( s.charAt( i ) == '!' || s.charAt( i ) == '>' 
		    ||  s.charAt( i ) == '<') {
		    char cmd = s.charAt( i );
		    i++;
		    int send = (int)s.charAt( i );
		    if( s.charAt( i ) == '\\' ) {
			i++;
			switch( s.charAt( i ) ) {
			case '\\': send = '\\'; break; // slash
			case 'n': send = '\n'; break;  // newline
			case 'p': send = '|'; break;   // pine
			case 'd': send = 0xFFFF; break;// delete
			case 'a': send = 0xFFE9; break;// alt
			case 'm': send = 0xFFE7; break;// meta
			case 'c': send = 0xFFE3; break;// control
			case '1': send = 0xFFbe; break;// F1
			case '2': send = 0xFFbf; break;// F2
			case '3': send = 0xFFb0; break;// F3
			case '4': send = 0xFFc1; break;// F4
			case '5': send = 0xFFc2; break;// F5
			case '6': send = 0xFFc3; break;// F6
			case '7': send = 0xFFc4; break;// F7
			case '8': send = 0xFFc5; break;// F8
			case '9': send = 0xFFc6; break;// F9
			case '0': send = 0xFFc7; break;// F10
			case 'A': send = 0xFFc8; break;// F11
			case 'B': send = 0xFFc9; break;// F12

			    // TODO the rest! :>
			default: send = s.charAt( i );
			}
		    }
		    switch( cmd ) {
		    case '!': midlet.rfb.key( send, true );
			midlet.rfb.key( send, false );
			break;
		    case '>': midlet.rfb.key( send, false ); break;
		    case '<': midlet.rfb.key( send, true ); break;
		    }
		} else if( s.charAt( i ) == 'm' ) {
		    switch( s.charAt( i+1 ) )  {
		    case 'u': i++; sendMouse( UP ); break;
		    case 'd': i++; sendMouse( DOWN ); break;	
		    case 'l': i++; sendMouse( LEFT ); break;
		    case 'r': i++; sendMouse( RIGHT ); break;
		    case '#': i++;
			//#ifdef DEBUG
			//# VNC.log( "Move Move: Going: " + s );
			//#endif
			mouseX += Integer.parseInt( s.substring( i+1, i+5 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Move: mouseX" + s.substring( i+1, 
			//# i+5 )  );
			//#endif
			mouseY += Integer.parseInt( s.substring( i+6, i+10 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Move: mouseY: " + s.substring( i+6, 
			//# i+10 ));
			//#endif
			if( mouseX < 0 ) mouseX = 0;
			if( mouseY < 0 ) mouseY = 0;
			if( mouseX > midlet.rfb.x ) mouseX = midlet.rfb.x;
			if( mouseX > midlet.rfb.y ) mouseX = midlet.rfb.y;
			//#ifdef DEBUG
			//# VNC.log( "Move Move: " + mouseX + "x" + mouseY );
			//#endif
			midlet.rfb.mouse( mouseX, mouseY, 0 );
			i=+9;
			break;
		    case '%': i++;
			//#ifdef DEBUG
			//# VNC.log( "Move Mouse %: Going: " + s );
			//#endif
			// Get 1 percent
			int xp = ( midlet.rfb.x ) / 100;
			int yp = ( midlet.rfb.y ) / 100;
			//#ifdef DEBUG
			//# VNC.log( "Move Move: mouseX" + s.substring( i+1, 
			//# i+4 )  );
			//#endif
			int deltaxp= Integer.parseInt( s.substring
						       ( i+1, i+4 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Move: mouseY: " + s.substring( i+5, 
			//# i+8 ));
			//#endif
			int deltayp= Integer.parseInt( s.substring
						       ( i+5, i+8 ) );	
			
			mouseX=deltaxp*xp;
			mouseY=deltayp*yp;
			// Should never happen.
			if( mouseX < 0 ) mouseX = 0;
			if( mouseY < 0 ) mouseY = 0;
			if( mouseX > midlet.rfb.x ) mouseX = midlet.rfb.x;
			if( mouseX > midlet.rfb.y ) mouseX = midlet.rfb.y;
			//#ifdef DEBUG
			//# VNC.log( "Move Move: " + mouseX + "x" + mouseY );
			//#endif
			midlet.rfb.mouse( mouseX, mouseY, 0 );
			i=+7;
			break;
		    default:
			//#ifdef DEBUG
			//# VNC.log( "Move Move: Going: " + s );
			//#endif
			mouseX = Integer.parseInt( s.substring( i+1, i+5 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Move: mouseX" + s.substring( i+1, 
			//# i+5 )  );
			//#endif
			mouseY = Integer.parseInt( s.substring( i+6, i+10 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Move: mouseY: " + s.substring( i+6, 
			//# i+10 ));
			//#endif
			if( mouseX < 0 ) mouseX = 0;
			if( mouseY < 0 ) mouseY = 0;
			if( mouseX > midlet.rfb.x ) mouseX = midlet.rfb.x;
			if( mouseX > midlet.rfb.y ) mouseX = midlet.rfb.y;
			//#ifdef DEBUG
			//# VNC.log( "Move Move: " + mouseX + "x" + mouseY );
			//#endif
			midlet.rfb.mouse( mouseX, mouseY, 0 );
			i=+9;
			break;
		    }		
		} else if( s.charAt( i ) == 'M' ) {
		    switch( s.charAt( i+1 ) ) {
		    case 'u': i++; sendNav( UP );    break;
		    case 'd': i++; sendNav( DOWN );  break;	
		    case 'l': i++; sendNav( LEFT );  break;
		    case 'r': i++; sendNav( RIGHT ); break;
		    case '#': i++;
			// TODO: Invalidate screen!
			offx += Integer.parseInt( s.substring( i+1, i+5 ) );
			offy += Integer.parseInt( s.substring( i+6, i+10 ) );

			if( offx < 0 ) offx = 0;
			if( offy < 0 ) offy = 0;
			if( offx > midlet.rfb.x - getWidth() ) 
			    offx = midlet.rfb.x - getWidth() ;
			if( offy > midlet.rfb.y - getHeight() ) 
			    offx = midlet.rfb.y - getHeight();
			incUpdate = false;
			i=+9;
			break;
		    case '%': i++;
			// TODO: Invalidate screen!
			//#ifdef DEBUG
			//# VNC.log( "Move Screen %: \"" + s.substring( i+1, i+4 ) + "\" - \"" + s.substring( i+5, i+8 ) + "\"" );
			//# VNC.log( "Move Screen %: Going: " + s + " " + i);
			//#endif
			int xp = ( midlet.rfb.x - getWidth()) / 100;
			int yp = ( midlet.rfb.y - getHeight() ) / 100;

			int deltaxp = Integer.parseInt( s.substring
						       ( i+1, i+4 ) );
			int deltayp = Integer.parseInt( s.substring
						       ( i+5, i+8 ) );	
			
			offx=deltaxp*xp;
			offy=deltayp*yp;

			if( offx < 0 ) offx = 0;
			if( offy < 0 ) offy = 0;
			if( offx > midlet.rfb.x - getWidth() ) 
			    offx = midlet.rfb.x - getWidth() ;
			if( offy > midlet.rfb.y - getHeight() ) 
			    offy = midlet.rfb.y - getHeight();

			incUpdate = false;
			
			i=+9;
			break;
		    default:
			// TODO: Invalidate screen!
			//#ifdef DEBUG
			//# VNC.log( "Move Screen: Going: " + s );
			//#endif
			int mX = Integer.parseInt( s.substring( i+1, i+5 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Screen: X" + s.substring( i+1, i+5 ) );
			//#endif
			int mY = Integer.parseInt( s.substring( i+6, i+10 ) );
			//#ifdef DEBUG
			//# VNC.log( "Move Screen: Y: " + s.substring( i+6,i+10 ));
			//#endif
			if( mX < 0 ) mX = 0;
			if( mY < 0 ) mY = 0;
			if( mX > midlet.rfb.x - getWidth() ) 
			    mX = midlet.rfb.x - getWidth() ;
			if( mY > midlet.rfb.y - getHeight() ) 
			    mY = midlet.rfb.y - getHeight();
			//#ifdef DEBUG
			//# VNC.log( "Move Screen: " + mX + "x" + mY );
			//#endif
			incUpdate = false;
			offx = mX;
			offy = mY;
			i=+9;
		    }
		
		} else if( s.charAt( i ) == 'c' ) {
		    //#ifdef DEBUG
		    //# VNC.log( "Move click "  );
		    //#endif
		    midlet.rfb.mouse( mouseX, mouseY, 1 );
		    midlet.rfb.mouse( mouseX, mouseY, 0 );
		} 
	    }
	} catch( IndexOutOfBoundsException io ) {
	    //#ifdef DEBUG
	    //# VNC.log( io.toString() );
	    //#endif
	    AlertType.ERROR.playSound( midlet.me );
	    midlet.me.setCurrent( new Alert( "#index_out_title#", 
					     "#index_out_start#" 
					     + io.getMessage() 
					     + "#index_out_end#", null, 
					     AlertType.ERROR), this );
	    
	} 
	midlet.rfb.requestUpdate( offx, offy, 
				  getWidth(), getHeight(), 
				  incUpdate );
    }

}


//  LocalWords:  drawImage sb mouseX mouseY
